package com.fxu.framework.core.service;

import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fxu.framework.core.sql.SEntity;
import com.fxu.framework.core.sql.SQuery;
import com.fxu.framework.core.sql.SUpdate;

import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;

/**
 * 通用顶级 Service
 *
 * @author 方旭
 * @version 1.0.0
 */
public interface ApiService {
    /**
     * findBy最大读取数量
     */
    int FIND_BY_MAX_SIZE = 3000;

    /**
     * 默认批次提交数量
     */
    int DEFAULT_BATCH_SIZE = 1000;

    /**
     * 保存一条记录（选择字段，策略插入，添加或者更新）
     * @param entity 实体对象
     * @param <SE> 实体对象范型类型
     * @return 是否成功（true:成功,false:失败）
     */
    <SE extends SEntity<SE>> boolean saveOne(@NotNull SE entity);

    /**
     * 批量保存（添加或者更新）
     * @param entityList 实体对象集合
     * @param <SE> 实体对象范型类型
     * @return 是否成功（true:成功,false:失败）
     */
    // 接口不能使用事务Transactional注解
    <SE extends SEntity<SE>> boolean saveBatch(@NotNull Collection<SE> entityList);

    /**
     * 忽略执行影响行数，保证效率
     * @param entityList 实体对象集合
     * @param <SE> 实体对象范型类型
     * @return 是否成功（true:成功,false:失败）
     */
    <SE extends SEntity<SE>> boolean saveBatchIgnore(@NotNull Collection<SE> entityList);

    /**
     * 为执行insert带有id的实体对象集合
     * 不执行级联保存
     * @param entityList 实体对象集合
     * @param <SE> 实体对象范型类型
     * @return 是否成功（true:成功,false:失败）
     */
    default <SE extends SEntity<SE>> boolean insertBatch(@NotNull Collection<SE> entityList) {
      return insertBatch(entityList, false);
    }

    /**
     * 为执行insert带有id的实体对象集合
     * 不执行级联保存
     * @param entityList 实体对象集合
     * @param ignore 是否忽略执行
     * @param <SE> 实体对象范型类型
     * @return 是否成功（true:成功,false:失败）
     */
    <SE extends SEntity<SE>> boolean insertBatch(@NotNull Collection<SE> entityList, boolean ignore);

    /**
     * 批量事务保存[用于执行多个不同表保存同时需要事物]
     * @param entityList 对象数组
     * @return 是否成功（true:成功,false:失败）
     */
    boolean saveBatchTx(@NotNull Collection<Object> entityList);

    /**
     * 单个更新
     * @param <SE> 实体对象范型类型
     * @param updateChain 需要保存的对象
     * @return 是否成功（true:成功,false:失败）
     */
    <SE extends SEntity<SE>> boolean updateBy(@NotNull SUpdate<SE> updateChain);

    /**
     * 批量更新[用于执行多个不同表更新,而且可以带上条件,update user set money = money - 1 where money &gt; 1]
     * @param updateChainList 需要保存的对象
     * @param <SE> 实体对象范型类型
     * @return 是否成功（true:成功,false:失败）
     */
    <SE extends SEntity<SE>> boolean updateBatchBy(@NotNull Collection<SUpdate<SE>> updateChainList);

    /**
     * 批量更新[用于执行多个不同表更新,而且可以带上条件,update user set money = money - 1 where money &gt; 1]
     * @param updateChainList 需要保存的对象
     * @return 是否成功（true:成功,false:失败）
     */
    boolean updateBatch(@NotNull Collection<SUpdate<?>> updateChainList);

    /**
     * 根据条件批量删除（暂时不支持级联删除）
     * @param queryChain 查询条件
     * @param <SE> 实体对象范型类型
     * @return 更新个数
     */
    <SE extends SEntity<SE>> int deleteBy(@NotNull SQuery<SE> queryChain);

    /**
     * 根据 ID 查询
     * @param clazz 实体Entity类
     * @param id 实体对象自增ID值
     * @param <SE> 实体对象范型类型
     * @return id对应的对象或者null
     */
    default <SE extends SEntity<SE>> SE findById(@NotNull Class<SE> clazz, @NotNull Serializable id) {
        return findOne(new SQuery<>(clazz).id(id));
    }

    /**
     * 根据条件查找第一个
     * @param queryChain 查询条件
     * @param <SE> 实体对象范型类型
     * @return 查找到的对象,可能返回null
     */
    default <SE extends SEntity<SE>> SE findFirst(@NotNull SQuery<SE> queryChain) {
        return findOne(queryChain.orderAsc(SQuery::idFun));
    }

    /**
     * 根据条件查找最后一个
     * @param queryChain 查询条件
     * @param <SE> 实体对象范型类型
     * @return 查找到的对象,可能返回null
     */
    default <SE extends SEntity<SE>> SE findLast(@NotNull SQuery<SE> queryChain) {
        return findOne(queryChain.orderDesc(SQuery::idFun));
    }

    /**
     * 根据条件查找一个对象
     * @param queryChain 查询条件
     * @param <SE> 实体对象范型类型
     * @return 查找到的一个对象,可能返回null
     */
    default <SE extends SEntity<SE>> SE findOne(@NotNull SQuery<SE> queryChain) {
        List<SE> list = findBy(1, queryChain);
        return null == list || list.size() <= 0 ? null : list.get(0);
    }

    /**
     * 根据字段in查找[最多查找3000条]
     * @param size 查找个数
     * @param queryChain 查询条件
     * @param <SE> 实体对象范型类型
     * @return 查找到的对象集合,不会返回null,只会返回空对象列表
     */
    default <SE extends SEntity<SE>> List<SE> findBy(int size, @NotNull SQuery<SE> queryChain) {
        return limitBy(1, Math.min(size, FIND_BY_MAX_SIZE), queryChain);
    }

    /**
     * 是否存在根据条件查找数据
     * @param queryChain 查询条件
     * @param <SE> 实体对象范型类型
     * @return 是否找到了对象
     */
    default <SE extends SEntity<SE>> boolean exists(@NotNull SQuery<SE> queryChain) {
        return null != findOne(queryChain.onlyShowId());
    }

    /**
     * 根据条件查找所有
     * limit XX 在数据量大的时候会乱使用索引导致效率低下，采用去掉limit
     * EXPLAIN SELECT id, update_time,zz_json FROM hjj_stat WHERE (project_id = 400 AND building_id = 0) ORDER BY id DESC; : ref = const
     * EXPLAIN SELECT id, update_time,zz_json FROM hjj_stat WHERE (project_id = 400 AND building_id = 0) ORDER BY id DESC LIMIT 1; : ref = NULL
     * @param queryChain 查询条件
     * @param <SE> 实体对象范型类型
     * @return 查找到的对象集合,不会返回null,只会返回空对象列表
     */
    <SE extends SEntity<SE>> List<SE> listBy(@NotNull SQuery<SE> queryChain);

    /**
     * 根据条件分页查找
     * @param page 第几页
     * @param size 每页个数
     * @param queryChain 查询条件
     * @param <SE> 实体对象范型类型
     * @return 查找到的分页对象,不会返回null,只会返回空对象列表
     */
    <SE extends SEntity<SE>> Page<SE> pageBy(int page, int size, @NotNull SQuery<SE> queryChain);

    /**
     * 根据条件分页查找
     * pageBy : 碰到数据量很大时候, select count(1) 会非常耗时
     * 每次使用pageBy 非常不合适，这个时候需要使用limitBy 这样效率高很多
     * 最极端的时候，直接使用limitBy读取数据，countBy读取分页个数
     * @param page 第几页
     * @param size 每页个数
     * @param queryChain 查询条件
     * @param <SE> 实体对象范型类型
     * @return 查找到的对象集合,不会返回null,只会返回空对象列表
     */
    <SE extends SEntity<SE>> List<SE> limitBy(int page, int size, @NotNull SQuery<SE> queryChain);

    /**
     * 根据条件分页查找
     * pageBy : 数据量很大时候, 会非常耗时, 可以使用offsetBy, 每次输入上一个最后一个ID, 速度会非常快
     * 此方法只适合 pri key 字段为 Integer 或者 Long 类型的表查找, 排序放在queryChain中
     * @param size 读取个数
     * @param nextId 上一页最后一个ID
     * @param queryChain 查询条件
     * @param <SE> 实体对象范型类型
     * @return 查找到的对象集合,不会返回null,只会返回空对象列表
     */
    <SE extends SEntity<SE>> List<SE> offsetBy(int size, Long nextId, @NotNull SQuery<SE> queryChain);

    /**
     * 根据条件查询数据数量
     * @param queryChain 查询条件
     * @param <SE> 实体对象范型类型
     * @return 查找到的总数
     */
    <SE extends SEntity<SE>> long countBy(@NotNull SQuery<SE> queryChain);

    /**
     * 根据条件得到排序列表
     * @param queryChain 查询条件
     * @param <SE> 实体Entity范型类型
     * @return 统计总和,不会返回null,没有统计数据返回0
     */
    default <SE extends SEntity<SE>> BigDecimal sumBy(@NotNull SQuery<SE> queryChain) {
        SE entity = sumsBy(queryChain);
        String sumField = queryChain.getSumFields().get(0).getName();
        Object obj = ReflectUtil.getFieldValue(entity, sumField);
        return obj == null ? BigDecimal.ZERO : new BigDecimal(obj.toString());
    }

    /**
     * 根据条件得到排序列表
     * @param queryChain 查询条件
     * @param <SE> 实体Entity范型类型
     * @return 统计总和列表,不会返回null,没有统计数据返回0列表
     */
    <SE extends SEntity<SE>> SE sumsBy(@NotNull SQuery<SE> queryChain);

    /**
     * 根据条件得到排序列表
     * @param queryChain 查询条件
     * @param <SE> 实体Entity范型类型
     * @return 返回根据group by统计数据集合,不会返回null,只会返回空对象列表
     */
    <SE extends SEntity<SE>> List<SE> groupSumBy(@NotNull SQuery<SE> queryChain);

    /**
     * 根据条件得到排序列表
     * @param size 读取几个
     * @param queryChain 查询条件
     * @param <SE> 实体Entity范型类型
     * @return 返回根据group by统计数据集合,不会返回null,只会返回空对象列表
     */
    <SE extends SEntity<SE>> List<SE> groupSumBy(int size, @NotNull SQuery<SE> queryChain);

    /**
     * 根据条件得到排序列表
     * @param page 第几页
     * @param size 每页个数
     * @param queryChain 查询条件
     * @param <SE> 实体Entity范型类型
     * @return 返回根据group by统计数据集合,不会返回null,只会返回空对象列表
     */
    <SE extends SEntity<SE>> Page<SE> groupSumBy(int page, int size, @NotNull SQuery<SE> queryChain);
}
